
package com.zhike.service.im;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.annotation.Resource;

import org.springframework.stereotype.Component;

import com.zhike.mobileimsdk.server.event.MessageQoSEventListenerS2C;
import com.zhike.mobileimsdk.server.protocal.Protocal;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.zhike.common.model.ChatAcks;
import com.zhike.common.model.ChatSession;
import com.zhike.mapper.messages.ChatAcksMapper;
import com.zhike.mapper.messages.ChatSessionMapper;
import com.zhike.service.im.concurrent.CommonConcurrentExecutor;

import lombok.extern.slf4j.Slf4j;

/**
 * 服务端的QoS消息送达保证机制的事件监听器.实现类。
 * <p>
 * <b>当前QoS机制支持全部的C2C、C2S、S2C共3种消息交互场景下的消息送达质量保证：<</b>>
 * <ur>
 * <li>1) Client to
 * Server(C2S)：即由某客户端主动发起，消息最终接收者是服务端，此模式下：重发由C保证、ACK应答由S发回；</li>
 * <li>2) Server to
 * Client(S2C)：即由服务端主动发起，消息最终接收者是某客户端，此模式下：重发由S保证、ACK应答由C发回；</li>
 * <li>2) Client to
 * Client(C2C)：即由客户端主动发起，消息最终接收者是另一客户端。此模式对于QoS机制来说，相当于C2S+S2C两程路径。</li>
 * </ul>
 * <p>
 * TCP理论上能从底层保证数据的可靠性，但应用层的代码和场景中存在网络本身和网络之外的可种不可靠性，
 * MobileIMSDK中的QoS送达保证机制，将加强TCP的可靠性，确保消息，无法从哪一个层面和维度，都会给
 * 开发者提供两种结果：要么明确被送达（即收到ACK应答包，见 {@link #messagesBeReceived(String)}）
 * 、要行明确未被送达（见 {@link #messagesLost(ArrayList)}）。从理论上，保证消息的百分百送达率。
 * 
 * @author Jack Jiang
 * @since 7.0
 * @see com.zhike.mobileimsdk.server.qos.QoS4SendDaemonS2C
 * @see com.zhike.mobileimsdk.server.qos.QoS4ReciveDaemonC2S
 * @see com.zhike.mobileimsdk.server.event.MessageQoSEventListenerS2C
 */
@Slf4j
@Component
public class MessageQoSEventListener implements MessageQoSEventListenerS2C {

  @Resource
  private ChatSessionMapper chatSessionMapper;

  @Resource
  private ChatAcksMapper chatAcksMapper;

  /**
   * 消息未送达的回调事件通知.
   * 
   * @param lostMessages 由MobileIMSDK QoS算法判定出来的未送达消息列表（此列表
   *                     中的Protocal对象是原对象的clone（即原对象的深拷贝），请放心使用哦），应用层
   *                     可通过指纹特征码找到原消息并可以UI上将其标记为”发送失败“以便即时告之用户
   */
  @Override
  public void messagesLost(final ArrayList<Protocal> lostMessages) {
    log.debug("【QoS_S2C事件[未实时送达]】收到系统的未实时送达事件通知，当前共有"
        + lostMessages.size() + "个包QoS保证机制结束，判定为【无法实时送达】！");

    if (lostMessages.size() > 0) {
      // 将处理放在独立的线程池中处理以提升处理效率，防止在大用户量时阻塞主服务
      CommonConcurrentExecutor.getInstance().execute(new Runnable() {
        @Override
        public void run() {
          // 大数据量的情况下，iterator性能要大大优于普通的数组for循环
          Iterator<Protocal> pit = lostMessages.iterator();
          while (pit.hasNext()) {
            Protocal p = pit.next();
            if (p != null) {
              // // 目前，对于应用层来说，只有群聊消息或指令是走S2C消息路径，所以此QoS事件处理中，
              // // 只需要针对群聊，一对一消息（包括好友和陌生人聊天）走的是MobileIMSDK的客户端QoS机制
              // if(p.getTypeu() == UserProtocalsType.MT45_OF_GROUP$CHAT$MSG_SERVER$TO$B)
              // {
              // log.debug("【DEBUG_QoS_S2C事件】未送达的是群聊消息，马上进行离线处理，fp="+p.getFp());
              // OfflineMessageManager.processOfflineMessage(p, false);
              // }

              try {
                // 一对一好友聊天的消息因走的是C2C消息路径，所以不需要在QoS机制中处理聊天记录了，
                // 因为它已在 ServerEventListener.onTransferMessage4C2C()中处理，这里再做就会重复哦
                // if (p.getTypeu() == UserProtocalsType.MT03_OF_CHATTING_MESSAGE) {
                // chatServerLauncher.getServerEventListener()
                // .onTransferMessage_RealTimeSendFaild_impl(p, true);
                // } else {
                // chatServerLauncher.getServerEventListener()
                // .onTransferMessage_RealTimeSendFaild_impl(p, false);
                // }
              } catch (Exception e) {
                log.warn("【QoS_S2C事件[未实时送达]】离线处理出错了，原因是：" + e.getMessage(), e);
              }
            }
          }
        }
      });
    }
  }

  /**
   * 消息已被对方收到的回调事件通知.
   * <p>
   * <b>目前，判定消息被对方收到是有两种可能：</b><br>
   * 1) 对方确实是在线并且实时收到了；<br>
   * 2) 对方不在线或者服务端转发过程中出错了，由服务端进行离线存储成功后的反馈
   * （此种情况严格来讲不能算是“已被收到”，但对于应用层来说，离线存储了的消息
   * 原则上就是已送达了的消息：因为用户下次登陆时肯定能通过HTTP协议取到）。
   * 
   * @param theFingerPrint 已被收到的消息的指纹特征码（唯一ID），应用层可据此ID
   *                       来找到原先已发生的消息并可在UI是将其标记为”已送达“或”已读“以便提升用户体验
   */
  @Override
  public void messagesBeReceived(String theFingerPrint) {
    if (theFingerPrint != null) {
      log.debug("【QoS_S2C事件】收到对方已收到消息事件的通知，fp= " + theFingerPrint);
      LambdaQueryWrapper<ChatSession> lqw = new LambdaQueryWrapper<>();
      lqw.eq(ChatSession::getFp, theFingerPrint);
      List<ChatSession> chatSessionList = chatSessionMapper.selectList(lqw);
      if (chatSessionList != null && chatSessionList.size() > 0) {
        ChatSession chatSession = chatSessionList.get(0);
        ChatAcks ack = new ChatAcks();
        ack.setFromUid(chatSession.getFromUid());
        ack.setToUid(chatSession.getToUid());
        ack.setSessionId(chatSession.getSessionId());
        ack.setFp(theFingerPrint);
        ack.setGroupId(chatSession.getGroupId());
        ack.setIfAck("1");
        chatAcksMapper.insert(ack);
      }
    }
  }
}
